/*
 *  WsvJdbc - a JDBC driver for WSV (values separated by whatever) data files
 *  Copyright (C) 2004  Daniele Pes
 * 
 *  CsvJdbc - a JDBC driver for CSV files
 *  Copyright (C) 2001  Jonathan Ackerman
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
package axs.jdbc.driver;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.concurrent.Executor;

import axs.jdbc.dataSourceConfiguration.JdbcSourceConfiguration;
import axs.jdbc.dataSpecification.SqlTypes;
import axs.jdbc.utils.ArrayUtilities;


/**
 * This class implements the Connection interface for the WsvJdbc driver.
 *
 * @author     Jonathan Ackerman
 * @author     Sander Brienen
 * @author     Michael Maraya
 * @author     Tomasz Skutnik
 * @author 		Daniele Pes
 * @author 		Marco Pozzato
 * @version    $Id: WsvConnection.java,v 1.2 2007/04/16 17:24:37 montag451 Exp $
 */

public class WsvConnection 
implements Connection {

	/** Directory where the CSV files to use are located */
   private String    path;
   
   /** File extension to use */
   private String    extension       = WsvDriver.DEFAULT_EXTENSION;
   
   /** Field separator to use */
	private String    separator       = String.valueOf(WsvDriver.DEFAULT_SEPARATOR);
   
   /** Should headers be suppressed */
   private boolean   suppressHeaders = WsvDriver.DEFAULT_SUPPRESS;
   
   /** Collection of all created Statements */
   private Vector    statements      = new Vector();
   
   /** Charset that should be used to read the files */
   private String    charset         = WsvDriver.DEFAULT_CHARSET;
   
   /** Stores whether this Connection is closed or not */
   private boolean   closed;

	/** Array of available columns names for referenced table*/
	private String[]  columnNames     = null;

	/** Array of available columns types for referenced table*/
	private Class[]   columnTypes     = null;

	/** Array of available columns boundaries for referenced table (in case they are specified using fixed lenght mode)*/
	private String[]  columnBounds    = null;

	/** Array of available String columns widths*/
	private Integer[] columnWidths    = null;
	
	/** This can be used to specify how many lines of the source data file have to be skipped*/
	private int 		skipLines 		 = 0;
	
	/** This can be used to specify one or more valid date formats*/
	private String		dateFormat		 = WsvDriver.DEFAULT_DATE_FORMAT;
	
	private String[]  dateFormats     = new String[]{dateFormat};
    
   /** if separator is fixed_length, trimIfString tells if String fields have to be trimmed*/
   private boolean[] trimIfVarchar; 
    
   /**
    * Creates a new CsvConnection that takes the supplied path
    * @param path directory where the CSV files are located
    */
   protected WsvConnection(String path) {
      // validate argument(s)
      if (path == null || path.length() == 0) {
         throw new IllegalArgumentException("'path' argument may not be empty or null");
      }
      this.path = path;
   }
   
   
   /**
    * Creates a new CsvConnection that takes the supplied path and properties.
    * The parameters that can be provided from the outer scope are:
    * 	- FILE_EXTENSION    If not specified, a default file extension ('.csv') will be assumed.
    * 	- SEPARATOR         If not specified, a default separator (',') will be used.
    * 	- SUPPRESS_HEADERS  If not specified, it will be assumed that the first line is not composed by the names of columns (true).
    * 	- CHARSET			  If not specified, a default char set (ISO-8859-1) will be used. The specifiable char sets are:
    * 									- US-ASCII Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set 
	*										- ISO-8859-1   ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1 
	*										- UTF-8 Eight-bit UCS Transformation Format 
	*										- UTF-16BE Sixteen-bit UCS Transformation Format, big-endian byte order
	*										- UTF-16LE Sixteen-bit UCS Transformation Format, little-endian byte order
	*										- UTF-16 Sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order mark 	
   * 	- COLUMN_NAMES  The names of columns
   * 	- COLUMN_TYPES  They can be specified either by the integer values as described in <code>java.sql.Types</code>
   * 						 or by their relating names according to this mapping (note that java mapping, as defined in 
   * 						 <code>java.sql.Types</code>, has been extended in order to encompass java primitive types):
   * 							VARCHAR   	  		- String.class			  12
 	* 							BOOLEAN   	  		- Boolean.class		  16
 	* 							TINYINT   	      - Byte.class			  -6
 	* 							CHAR	     	      - Character.class			1
 	* 							DOUBLE    	  		- Double.class				8
 	* 							FLOAT     	      - Float.class				6
 	* 							INTEGER   	  		- Integer.class			4
 	* 							BIGINT    	      - Long.class			  -5
 	* 							SMALLINT  	      - Short.class				5
 	* 							TIMESTAMP         - Timestamp.class		  93
 	* 							DATE					- Date.class			  91
 	* 							TIME      	  		- Time.class			  92
 	* 							VARBINARY 	  		- byte[].class			  -3
 	* 							NULL      	  		- null			  			0
 	* 							JAVA_OBJECT 	   - Object.class   	   2000
 	* 							current gap for primitive types is 10000
 	* 							P_BOOLEAN 	  		- boolean.class	 100016
 	* 							P_TINYINT 	  		- byte.class			9994
 	* 							P_CHAR    	  		- char.class		 100001
 	* 							P_DOUBLE  	  		- double.class		 100008
 	* 							P_FLOAT   	  		- float.class		 100006
 	* 							P_INTEGER 	  		- int.class			 100004
 	* 							P_BIGINT  	  		- long.class			9995
 	* 							P_SMALLINT 	  		- short.class		 100005
    *    - COLUMN_WIDTHS The max widths of String typed columns.
    *    - COLUMN_BOUNDS The beginning and ending position of data columns in a fixed lenght plain text data file.
    * Column names and column types must be specified in <code>sourceDataProperties</code>.
    * @param sourceDataProperties The directory where the CSV files are located.
    * @param info The set of properties containing customized options.
    */
   protected WsvConnection(String     path, 
   						      Properties sourceDataProperties) 
   throws SQLException{
      this(path);
      // check for properties
      if (sourceDataProperties != null) {
         // set the file extension to be used
         if (sourceDataProperties.getProperty(WsvDriver.FILE_EXTENSION_PROP_NAME) != null) {
            extension = sourceDataProperties.getProperty(WsvDriver.FILE_EXTENSION_PROP_NAME, WsvDriver.DEFAULT_EXTENSION);       
         }
         // set the separator character to be used
         if (sourceDataProperties.getProperty(WsvDriver.SEPARATOR_PROP_NAME) != null) {
            separator = sourceDataProperties.getProperty(WsvDriver.SEPARATOR_PROP_NAME, String.valueOf(WsvDriver.DEFAULT_SEPARATOR));  
         }
         // set the header suppression flag
         if (sourceDataProperties.getProperty(WsvDriver.SUPPRESS_HEADERS_PROP_NAME) != null) {
            suppressHeaders = Boolean.valueOf(sourceDataProperties.getProperty(WsvDriver.SUPPRESS_HEADERS_PROP_NAME, String.valueOf(WsvDriver.DEFAULT_SUPPRESS))).booleanValue();
         }
         // default charset
         if (sourceDataProperties.getProperty(WsvDriver.CHARSET_PROP_NAME) != null) {
            charset   = sourceDataProperties.getProperty(WsvDriver.CHARSET_PROP_NAME, WsvDriver.DEFAULT_CHARSET);
         }
         // skip lines
			if (sourceDataProperties.getProperty(WsvDriver.SKIP_LINES_PROP_NAME) != null) {
         	skipLines = Integer.valueOf(sourceDataProperties.getProperty(WsvDriver.SKIP_LINES_PROP_NAME, String.valueOf(skipLines))).intValue();
			}
         //			
			columnNames  = getNames(sourceDataProperties.getProperty(WsvDriver.COLUMN_NAMES_PROP_NAME));			
			columnTypes  = getTypes(sourceDataProperties.getProperty(WsvDriver.COLUMN_TYPES_PROP_NAME));
			columnWidths = getWidths(sourceDataProperties.getProperty(WsvDriver.COLUMN_WIDTHS_PROP_NAME));
			columnBounds = getBoundaries(sourceDataProperties.getProperty(WsvDriver.COLUMN_BOUNDARIES_PROP_NAME));
			dateFormats  = getDateFormats(sourceDataProperties.getProperty(WsvDriver.DATE_FORMAT_PROP_NAME));
         //
         if(getSeperator().trim().equalsIgnoreCase(JdbcSourceConfiguration.FIXED_LENGHT_SEPARATOR))
            setTrimIfString(getColTrimIfString(sourceDataProperties.getProperty(WsvDriver.COLUMN_FIXED_LENGTH_TO_TRIM_PROP_NAME)));
      }
   }   

   

   /**
    * @return an array of boolean specifying which VARCHAR/String columns have to be trimmed, in case of 
    * fixed length saparated data file 
    */
   private boolean[] getColTrimIfString(String trimFixedLengthStringProperty)
   throws SQLException{
       boolean[] result = null;
      
      if (trimFixedLengthStringProperty != null && !trimFixedLengthStringProperty.equals("")) {                   
         StringTokenizer tokens = new StringTokenizer(trimFixedLengthStringProperty, String.valueOf(JdbcSourceConfiguration.PROPERTY_SEPARATOR));        
         Boolean[] trimEnablings = new Boolean[howManyColumns(trimFixedLengthStringProperty)];
         int idx = 0;
         while (tokens.hasMoreTokens()) { // if at least a minimal association is defined            
             trimEnablings[idx++] = Boolean.valueOf(tokens.nextToken());
         }
         Vector toBeTrimmed = null;
         if(trimEnablings.length > 0){
            toBeTrimmed = new Vector();
            result = new boolean[trimEnablings.length];
            for (int i = 0; i < trimEnablings.length; i++) {
               if(trimEnablings[i] != null){
                  result[i] = trimEnablings[i].booleanValue();
                  if(result[i])
                     toBeTrimmed.add(getColumnName(i));
               }
               else
                  result[i] = false;
            }
         }
         System.out.println("Columns " + toBeTrimmed + " (fixed length separated VARCHARs) will be trimmed");
      }
      else{
         result = new boolean[getColumnNames().length];
         for (int i = 0; i < result.length; i++) {
            result[i] = false;   
         }         
         System.out.println("Fixed length VARCHAR column won't be trimmed! ");
      }                       
      return result;
   }
   
   /**
	 * @param string
	 * @return
	 */
	private String[] getDateFormats(String dateFormatProperty)
	throws SQLException{
		String[] result = null;

		if (dateFormatProperty != null && !dateFormatProperty.equals("")) {
			StringTokenizer tokens;
			//
			tokens = new StringTokenizer(dateFormatProperty, String.valueOf(JdbcSourceConfiguration.DATE_FORMAT_SEPARATOR));
			Vector r = new Vector();					
			while (tokens.hasMoreTokens()) { // if at least a minimal association is defined
				r.add(tokens.nextToken());							
			}
			result = new String[r.size()];
			r.copyInto(result);
			System.out.println(result.length + " valid date formats were specified\n" + ArrayUtilities.printArray(result));
		}
		else{
			result = dateFormats;
			System.out.println("No date formats were specified -> default (ISO: yyyy-MM-dd HH:mm:ss.SSS) will be assumed.");
		}						
		return result;
	}


	private String[] getNames(String namesProperty)
   throws SQLException{
		String[] result = null;

		if (namesProperty != null && !namesProperty.equals("")) {
			StringTokenizer tokens;
			int 				 idx;
			//
			tokens = new StringTokenizer(namesProperty, String.valueOf(JdbcSourceConfiguration.PROPERTY_SEPARATOR));		
		   result = new String[howManyColumns(namesProperty)];
			idx = 0;
			while (tokens.hasMoreTokens()) { // if at least a minimal association is defined			
				result[idx++] = tokens.nextToken();
			}
			System.out.println(result.length + " columns were specified by their names (they can be identified)");
		}
		else{
			String howAssumed = suppressHeaders == true ? "(COLUMNi will be assumed)": "(they will be desumed from the first line of data file)";
			System.out.println("Warning: no column names were specified! " + howAssumed);
		}						
		return result;
   }
   

   private Class[] getTypes(String typesProperty)
	throws SQLException{
		Class[] result = null;

		if (typesProperty != null && !typesProperty.equals("")) {
			StringTokenizer tokens;			
			tokens = new StringTokenizer(typesProperty, String.valueOf(JdbcSourceConfiguration.PROPERTY_SEPARATOR));
			result = new Class[howManyColumns(typesProperty)]; // howManyColumns is >= 0
			int idx = 0;
			while (tokens.hasMoreTokens()) { // if at least a minimal association is defined				
				result[idx++] = SqlTypes.getAvalidSqlType(tokens.nextToken());		
			}
			if(columnNames != null && result.length != columnNames.length)
				throw new SQLException("Bad columns specification: column types specification is not coherent with that of column names!");
			else if(columnNames == null){
				System.out.println("Warning: only column types (" + result.length + "), no column names, were specified.");				
			}
			else{
				System.out.println(result.length + " columns types were specified");
			}						
		}
		else{
			System.out.println("Warning: no column types were specified (VARCHAR will be assumed).");
		}
		return result;		
   }
   

	private String[] getBoundaries(String boundariesProperty)
	throws SQLException{
		String[] result = null;

		if (boundariesProperty != null && !boundariesProperty.equals("")) {
			StringTokenizer tokens;			
			//
			tokens = new StringTokenizer(boundariesProperty, String.valueOf(JdbcSourceConfiguration.PROPERTY_SEPARATOR));
			result = new String[howManyColumns(boundariesProperty)];
			int idx = 0;
			while (tokens.hasMoreTokens()) { // if at least a minimal association is defined				
				result[idx] = tokens.nextToken();
				if(result[idx] == null || result[idx].equals(""))
					throw new SQLException("Invalid data specification: column boundaries specification is not coherent with that of column names! (" + idx + "th column specification is missing)");
				idx++; 				
			}
			if(columnNames != null && result.length != columnNames.length)
				throw new SQLException("Bad columns specification: column boundaries specification is not coherent with that of column names! (different number of elements)");
			else if(columnNames == null){
				System.out.println("Warning: only column boundaries (" + result.length + "), no column names, were specified (this is enough for columns to be identified).");				
			}
			else{
				System.out.println(result.length + " columns were specified by boundaries (this is enough for columns to be identified)");
			}	
		}
		else{ // boundariesProperty was not specified
			if(separator.trim().equals(JdbcSourceConfiguration.FIXED_LENGHT_SEPARATOR))
				throw new SQLException("Invalid data specification. Column boundaries specification is not coherent: separator is set to " + separator + " while it was not specified among other properties!");
		}
		return result;
	}


	private Integer[] getWidths(String widthsProperty)
	throws SQLException{
		Integer[] result = null;

		if (widthsProperty != null && !widthsProperty.equals("")) {
			StringTokenizer tokens;			
			//
			tokens = new StringTokenizer(widthsProperty, String.valueOf(JdbcSourceConfiguration.PROPERTY_SEPARATOR));
			result = new Integer[howManyColumns(widthsProperty)];
			int 	 idx = 0;
			String curToken;
			while (tokens.hasMoreTokens()) { // if at least a minimal association is defined
				curToken = tokens.nextToken();				
				result[idx++] = curToken.equals("null") || curToken.equals("") ? null : Integer.valueOf(curToken); 				
			}
			if(columnNames != null && result.length != columnNames.length)
				throw new SQLException("Bad columns specification: String column widths specification is not coherent with that of column names! (different number of elements)");
			else if(columnNames == null){
				System.out.println("Warning: only column widths (" + result.length + "), no column names, were specified.");
			}
			else{
				System.out.println("Got " + result.length + " columns widths.");				
			}
		}
		return result;
	}


   private int howManyColumns(String property){
   	int result = 0;
		if (property != null) {			
			StringTokenizer tokens = new StringTokenizer(property, JdbcSourceConfiguration.PROPERTY_SEPARATOR); //String.valueOf(separator));
			while (tokens.hasMoreTokens()) { // if at least a minimal association is defined
				tokens.nextToken();
				result++;
			}		
		}
		return result;
   }
   
   
   /**
    * Creates a <code>Statement</code> object for sending
    * SQL statements to the database.
    * SQL statements without parameters are normally
    * executed using <code>Statement</code> objects. If the same SQL statement
    * is executed many times, it may be more efficient to use a
    * <code>PreparedStatement</code> object.
    * <P>
    * Result sets created using the returned <code>Statement</code>
    * object will by default be type <code>TYPE_FORWARD_ONLY</code>
    * and have a concurrency level of <code>CONCUR_READ_ONLY</code>.
    *
    * @return a new default <code>Statement</code> object
    * @exception SQLException if a database access error occurs
    */
   public Statement createStatement() throws SQLException {
      WsvStatement statement;
      //
		statement = new WsvStatement(this);
      statements.add(statement);
      return statement;
   }
   
   
   /**
    * Creates a <code>PreparedStatement</code> object for sending
    * parameterized SQL statements to the database.
    * <P>
    * A SQL statement with or without IN parameters can be
    * pre-compiled and stored in a <code>PreparedStatement</code> object. This
    * object can then be used to efficiently execute this statement
    * multiple times.
    *
    * <P><B>Note:</B> This method is optimized for handling
    * parametric SQL statements that benefit from precompilation. If
    * the driver supports precompilation,
    * the method <code>prepareStatement</code> will send
    * the statement to the database for precompilation. Some drivers
    * may not support precompilation. In this case, the statement may
    * not be sent to the database until the <code>PreparedStatement</code>
    * object is executed.  This has no direct effect on users; however, it does
    * affect which methods throw certain <code>SQLException</code> objects.
    * <P>
    * Result sets created using the returned <code>PreparedStatement</code>
    * object will by default be type <code>TYPE_FORWARD_ONLY</code>
    * and have a concurrency level of <code>CONCUR_READ_ONLY</code>.
    *
    * @param sql an SQL statement that may contain one or more '?' IN
    * parameter placeholders
    * @return a new default <code>PreparedStatement</code> object containing the
    * pre-compiled SQL statement
    * @exception SQLException if a database access error occurs
    */
   public PreparedStatement prepareStatement(String sql) throws SQLException {
      throw new UnsupportedOperationException("Connection.prepareStatement(String) unsupported");
   }
   
   
   /**
    * Creates a <code>CallableStatement</code> object for calling
    * database stored procedures.
    * The <code>CallableStatement</code> object provides
    * methods for setting up its IN and OUT parameters, and
    * methods for executing the call to a stored procedure.
    *
    * <P><B>Note:</B> This method is optimized for handling stored
    * procedure call statements. Some drivers may send the call
    * statement to the database when the method <code>prepareCall</code>
    * is done; others
    * may wait until the <code>CallableStatement</code> object
    * is executed. This has no
    * direct effect on users; however, it does affect which method
    * throws certain SQLExceptions.
    * <P>
    * Result sets created using the returned <code>CallableStatement</code>
    * object will by default be type <code>TYPE_FORWARD_ONLY</code>
    * and have a concurrency level of <code>CONCUR_READ_ONLY</code>.
    *
    * @param sql an SQL statement that may contain one or more '?'
    * parameter placeholders. Typically this  statement is a JDBC
    * function call escape string.
    * @return a new default <code>CallableStatement</code> object containing the
    * pre-compiled SQL statement
    * @exception SQLException if a database access error occurs
    */
   public CallableStatement prepareCall(String sql) throws SQLException {
      throw new UnsupportedOperationException("Connection.prepareCall(String) unsupported");
   }
   
   
   /**
    * Converts the given SQL statement into the system's native SQL grammar.
    * A driver may convert the JDBC SQL grammar into its system's
    * native SQL grammar prior to sending it. This method returns the
    * native form of the statement that the driver would have sent.
    *
    * @param sql an SQL statement that may contain one or more '?'
    * parameter placeholders
    * @return the native form of this statement
    * @exception SQLException if a database access error occurs
    */
   public String nativeSQL(String sql) throws SQLException {
      throw new UnsupportedOperationException("Connection.nativeSQL(String) unsupported");
   }
   
   
   /**
    * Sets this connection's auto-commit mode to the given state.
    * If a connection is in auto-commit mode, then all its SQL
    * statements will be executed and committed as individual
    * transactions.  Otherwise, its SQL statements are grouped into
    * transactions that are terminated by a call to either
    * the method <code>commit</code> or the method <code>rollback</code>.
    * By default, new connections are in auto-commit
    * mode.
    * <P>
    * The commit occurs when the statement completes or the next
    * execute occurs, whichever comes first. In the case of
    * statements returning a <code>ResultSet</code> object,
    * the statement completes when the last row of the
    * <code>ResultSet</code> object has been retrieved or the
    * <code>ResultSet</code> object has been closed. In advanced cases, a
    * single statement may return multiple results as well as output
    * parameter values. In these cases, the commit occurs when all results and
    * output parameter values have been retrieved.
    * <P>
    * <B>NOTE:</B>  If this method is called during a transaction, the
    * transaction is committed.
    *
    * @param autoCommit <code>true</code> to enable auto-commit mode;
    *         <code>false</code> to disable it
    * @exception SQLException if a database access error occurs
    * @see #getAutoCommit
    */
   public void setAutoCommit(boolean autoCommit) throws SQLException {
      throw new UnsupportedOperationException("Connection.setAutoCommit(boolean) unsupported");
   }
   
   
   /**
    * Retrieves the current auto-commit mode for this <code>Connection</code>
    * object.
    *
    * @return the current state of this <code>Connection</code> object's
    *         auto-commit mode
    * @exception SQLException if a database access error occurs
    * @see #setAutoCommit
    */
   public boolean getAutoCommit() throws SQLException {
      //throw new UnsupportedOperationException("Connection.getAutoCommit() unsupported");
      return false; // at present this is just a read only driver: you can't do update/insert operations -> it
      				  // makes no sense giving the chance to set auto commit, so this is always false
   }
   
   
   /**
    * Makes all changes made since the previous
    * commit/rollback permanent and releases any database locks
    * currently held by this <code>Connection</code> object.
    * This method should be
    * used only when auto-commit mode has been disabled.
    *
    * @exception SQLException if a database access error occurs or this
    *            <code>Connection</code> object is in auto-commit mode
    * @see #setAutoCommit
    */
   public void commit() throws SQLException {
      throw new UnsupportedOperationException("Connection.commit() unsupported");
   }
   
   
   /**
    * Undoes all changes made in the current transaction
    * and releases any database locks currently held
    * by this <code>Connection</code> object. This method should be
    * used only when auto-commit mode has been disabled.
    *
    * @exception SQLException if a database access error occurs or this
    *            <code>Connection</code> object is in auto-commit mode
    * @see #setAutoCommit
    */
   public void rollback() throws SQLException {
      throw new UnsupportedOperationException("Connection.rollback() unsupported");
   }
   
   
   /**
    * Releases this <code>Connection</code> object's database and JDBC
    * resources immediately instead of waiting for them to be automatically
    * released.
    * <P>
    * Calling the method <code>close</code> on a <code>Connection</code>
    * object that is already closed is a no-op.
    * <P>
    * <B>Note:</B> A <code>Connection</code> object is automatically
    * closed when it is garbage collected. Certain fatal errors also
    * close a <code>Connection</code> object.
    *
    * @exception SQLException if a database access error occurs
    */
   public void close() throws SQLException {
      // close all created statements
      for (Enumeration i = statements.elements(); i.hasMoreElements();) {
         WsvStatement statement = (WsvStatement) i.nextElement();
         statement.close();
      }
      // set this Connection as closed
      closed = true;
   }
   
   
   /**
    * Retrieves whether this <code>Connection</code> object has been
    * closed.  A connection is closed if the method <code>close</code>
    * has been called on it or if certain fatal errors have occurred.
    * This method is guaranteed to return <code>true</code> only when
    * it is called after the method <code>Connection.close</code> has
    * been called.
    * <P>
    * This method generally cannot be called to determine whether a
    * connection to a database is valid or invalid.  A typical client
    * can determine that a connection is invalid by catching any
    * exceptions that might be thrown when an operation is attempted.
    *
    * @return <code>true</code> if this <code>Connection</code> object
    *         is closed; <code>false</code> if it is still open
    * @exception SQLException if a database access error occurs
    */
   public boolean isClosed() throws SQLException {
      return closed;
   }
   
   
   /**
    * Retrieves a <code>DatabaseMetaData</code> object that contains
    * metadata about the database to which this
    * <code>Connection</code> object represents a connection.
    * The metadata includes information about the database's
    * tables, its supported SQL grammar, its stored
    * procedures, the capabilities of this connection, and so on.
    *
    * @return a <code>DatabaseMetaData</code> object for this
    *         <code>Connection</code> object
    * @exception SQLException if a database access error occurs
    */
   public DatabaseMetaData getMetaData() throws SQLException {
      throw new UnsupportedOperationException("Connection.getMetaData() unsupported");
   }
   
   
   /**
    * Puts this connection in read-only mode as a hint to the driver to enable
    * database optimizations.
    *
    * <P><B>Note:</B> This method cannot be called during a transaction.
    *
    * @param readOnly <code>true</code> enables read-only mode;
    *        <code>false</code> disables it
    * @exception SQLException if a database access error occurs or this
    *            method is called during a transaction
    */
   public void setReadOnly(boolean readOnly) throws SQLException {
      throw new UnsupportedOperationException("Connection.setReadOnly(boolean) unsupported");
   }
   
   
   /**
    * Retrieves whether this <code>Connection</code>
    * object is in read-only mode.
    *
    * @return <code>true</code> if this <code>Connection</code> object
    *         is read-only; <code>false</code> otherwise
    * @exception SQLException if a database access error occurs
    */
   public boolean isReadOnly() throws SQLException {
      return true;
   }
   
   
   /**
    * Sets the given catalog name in order to select
    * a subspace of this <code>Connection</code> object's database
    * in which to work.
    * <P>
    * If the driver does not support catalogs, it will
    * silently ignore this request.
    *
    * @param catalog the name of a catalog (subspace in this
    *        <code>Connection</code> object's database) in which to work
    * @exception SQLException if a database access error occurs
    * @see #getCatalog
    */
   public void setCatalog(String catalog) throws SQLException {
      // silently ignore this request
   }
   
   
   /**
    * Retrieves this <code>Connection</code> object's current catalog name.
    *
    * @return the current catalog name or <code>null</code> if there is none
    * @exception SQLException if a database access error occurs
    * @see #setCatalog
    */
   public String getCatalog() throws SQLException {
      return null;
   }
   
   
   /**
    * Attempts to change the transaction isolation level for this
    * <code>Connection</code> object to the one given.
    * The constants defined in the interface <code>Connection</code>
    * are the possible transaction isolation levels.
    * <P>
    * <B>Note:</B> If this method is called during a transaction, the result
    * is implementation-defined.
    *
    * @param level one of the following <code>Connection</code> constants:
    *        <code>Connection.TRANSACTION_READ_UNCOMMITTED</code>,
    *        <code>Connection.TRANSACTION_READ_COMMITTED</code>,
    *        <code>Connection.TRANSACTION_REPEATABLE_READ</code>, or
    *        <code>Connection.TRANSACTION_SERIALIZABLE</code>.
    *        (Note that <code>Connection.TRANSACTION_NONE</code> cannot be used
    *        because it specifies that transactions are not supported.)
    * @exception SQLException if a database access error occurs
    *            or the given parameter is not one of the <code>Connection</code>
    *            constants
    * @see DatabaseMetaData#supportsTransactionIsolationLevel
    * @see #getTransactionIsolation
    */
   public void setTransactionIsolation(int level) throws SQLException {
      throw new UnsupportedOperationException("Connection.setTransactionIsolation(int) unsupported");
   }
   
   
   /**
    * Retrieves this <code>Connection</code> object's current
    * transaction isolation level.
    *
    * @return the current transaction isolation level, which will be one
    *         of the following constants:
    *        <code>Connection.TRANSACTION_READ_UNCOMMITTED</code>,
    *        <code>Connection.TRANSACTION_READ_COMMITTED</code>,
    *        <code>Connection.TRANSACTION_REPEATABLE_READ</code>,
    *        <code>Connection.TRANSACTION_SERIALIZABLE</code>, or
    *        <code>Connection.TRANSACTION_NONE</code>.
    * @exception SQLException if a database access error occurs
    * @see #setTransactionIsolation
    */
   public int getTransactionIsolation() throws SQLException {
      return Connection.TRANSACTION_NONE;
   }
   
   
   /**
    * Retrieves the first warning reported by calls on this
    * <code>Connection</code> object.  If there is more than one
    * warning, subsequent warnings will be chained to the first one
    * and can be retrieved by calling the method
    * <code>SQLWarning.getNextWarning</code> on the warning
    * that was retrieved previously.
    * <P>
    * This method may not be
    * called on a closed connection; doing so will cause an
    * <code>SQLException</code> to be thrown.
    *
    * <P><B>Note:</B> Subsequent warnings will be chained to this
    * SQLWarning.
    *
    * @return the first <code>SQLWarning</code> object or <code>null</code>
    *         if there are none
    * @exception SQLException if a database access error occurs or
    *            this method is called on a closed connection
    * @see SQLWarning
    */
   public SQLWarning getWarnings() throws SQLException {
      throw new UnsupportedOperationException("Connection.getWarnings() unsupported");
   }
   
   
   /**
    * Clears all warnings reported for this <code>Connection</code> object.
    * After a call to this method, the method <code>getWarnings</code>
    * returns <code>null</code> until a new warning is
    * reported for this <code>Connection</code> object.
    *
    * @exception SQLException if a database access error occurs
    */
   public void clearWarnings() throws SQLException {
      throw new UnsupportedOperationException("Connection.getWarnings() unsupported");
   }
   
   
   //--------------------------JDBC 2.0-----------------------------
   /**
    * Creates a <code>Statement</code> object that will generate
    * <code>ResultSet</code> objects with the given type and concurrency.
    * This method is the same as the <code>createStatement</code> method
    * above, but it allows the default result set
    * type and concurrency to be overridden.
    *
    * @param resultSetType a result set type; one of
    *        <code>ResultSet.TYPE_FORWARD_ONLY</code>,
    *        <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or
    *        <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
    * @param resultSetConcurrency a concurrency type; one of
    *        <code>ResultSet.CONCUR_READ_ONLY</code> or
    *        <code>ResultSet.CONCUR_UPDATABLE</code>
    * @return a new <code>Statement</code> object that will generate
    *         <code>ResultSet</code> objects with the given type and
    *         concurrency
    * @exception SQLException if a database access error occurs
    *         or the given parameters are not <code>ResultSet</code>
    *         constants indicating type and concurrency
    */
   public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
      throw new UnsupportedOperationException("Connection.createStatement(int, int) unsupported");
   }
   
   
   /**
    * Creates a <code>PreparedStatement</code> object that will generate
    * <code>ResultSet</code> objects with the given type and concurrency.
    * This method is the same as the <code>prepareStatement</code> method
    * above, but it allows the default result set
    * type and concurrency to be overridden.
    *
    * @param sql a <code>String</code> object that is the SQL statement to
    *            be sent to the database; may contain one or more ? IN
    *            parameters
    * @param resultSetType a result set type; one of
    *         <code>ResultSet.TYPE_FORWARD_ONLY</code>,
    *         <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or
    *         <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
    * @param resultSetConcurrency a concurrency type; one of
    *         <code>ResultSet.CONCUR_READ_ONLY</code> or
    *         <code>ResultSet.CONCUR_UPDATABLE</code>
    * @return a new PreparedStatement object containing the
    * pre-compiled SQL statement that will produce <code>ResultSet</code>
    * objects with the given type and concurrency
    * @exception SQLException if a database access error occurs
    *         or the given parameters are not <code>ResultSet</code>
    *         constants indicating type and concurrency
    */
   public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
      throws SQLException {
      throw new UnsupportedOperationException("Connection.prepareStatement(String, int, int) unsupported");
   }
   
   
   /**
    * Creates a <code>CallableStatement</code> object that will generate
    * <code>ResultSet</code> objects with the given type and concurrency.
    * This method is the same as the <code>prepareCall</code> method
    * above, but it allows the default result set
    * type and concurrency to be overridden.
    *
    * @param sql a <code>String</code> object that is the SQL statement to
    *            be sent to the database; may contain on or more ? parameters
    * @param resultSetType a result set type; one of
    *         <code>ResultSet.TYPE_FORWARD_ONLY</code>,
    *         <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or
    *         <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
    * @param resultSetConcurrency a concurrency type; one of
    *         <code>ResultSet.CONCUR_READ_ONLY</code> or
    *         <code>ResultSet.CONCUR_UPDATABLE</code>
    * @return a new <code>CallableStatement</code> object containing the
    * pre-compiled SQL statement that will produce <code>ResultSet</code>
    * objects with the given type and concurrency
    * @exception SQLException if a database access error occurs
    *         or the given parameters are not <code>ResultSet</code>
    *         constants indicating type and concurrency
    */
   public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
      throw new UnsupportedOperationException("Connection.prepareCall(String, int, int) unsupported");
   }
   
   
   /**
    * Retrieves the <code>Map</code> object associated with this
    * <code>Connection</code> object.
    * Unless the application has added an entry, the type map returned
    * will be empty.
    *
    * @return the <code>java.util.Map</code> object associated
    *         with this <code>Connection</code> object
    * @exception SQLException if a database access error occurs
    * @see #setTypeMap
    */
   public Map getTypeMap() throws SQLException {
      throw new UnsupportedOperationException("Connection.getTypeMap() unsupported");
   }
   
   
   /**
    * Installs the given <code>TypeMap</code> object as the type map for
    * this <code>Connection</code> object.  The type map will be used for the
    * custom mapping of SQL structured types and distinct types.
    *
    * @param map the <code>java.util.Map</code> object to install
    *        as the replacement for this <code>Connection</code>
    *        object's default type map
    * @exception SQLException if a database access error occurs or
    *        the given parameter is not a <code>java.util.Map</code>
    *        object
    * @see #getTypeMap
    */
   public void setTypeMap(Map map) throws SQLException {
      throw new UnsupportedOperationException("Connection.setTypeMap(Map) unsupported");
   }
   
   
   //--------------------------JDBC 3.0-----------------------------
   /**
    * Changes the holdability of <code>ResultSet</code> objects
    * created using this <code>Connection</code> object to the given
    * holdability.
    *
    * @param holdability a <code>ResultSet</code> holdability constant; one of
    *        <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
    *        <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
    * @throws SQLException if a database access occurs, the given parameter
    *         is not a <code>ResultSet</code> constant indicating holdability,
    *         or the given holdability is not supported
    * @since 1.4
    * @see #getHoldability
    * @see java.sql.ResultSet
    */
   public void setHoldability(int holdability) throws SQLException {
      throw new UnsupportedOperationException("Connection.setHoldability(int) unsupported");
   }
   
   
   /**
    * Retrieves the current holdability of ResultSet objects created
    * using this Connection object.
    *
    * @return the holdability, one of <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
    * <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
    * @throws SQLException if a database access occurs
    * @since 1.4
    * @see #setHoldability
    * @see java.sql.ResultSet
    */
   public int getHoldability() throws SQLException {
      throw new UnsupportedOperationException("Connection.getHoldability() unsupported");
   }
   
   
   /* Removed since this only builds under JDK 1.4
   public Savepoint setSavepoint() throws SQLException {
       throw new UnsupportedOperationException("Connection.setSavepoint() unsupported");
   }
   
   public Savepoint setSavepoint(String name) throws SQLException {
       throw new UnsupportedOperationException("Connection.setSavepoint(String) unsupported");
   }
   
   public void rollback(Savepoint savepoint) throws SQLException {
       throw new UnsupportedOperationException("Connection.rollback(Savepoint) unsupported");
   }
   
   public void releaseSavepoint(Savepoint savepoint) throws SQLException {
       throw new UnsupportedOperationException("Connection.releaseSavepoint(Savepoint) unsupported");
   }
    */
    
    
   public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
      throws SQLException {
      throw new UnsupportedOperationException("Connection.createStatement(int,int,int) unsupported");
   }
   
   
   public PreparedStatement prepareStatement(
      String sql,
      int resultSetType,
      int resultSetConcurrency,
      int resultSetHoldability)
      throws SQLException {
      throw new UnsupportedOperationException("Connection.prepareStatement(String,int,int,int) unsupported");
   }
   
   
   public CallableStatement prepareCall(
      String sql,
      int resultSetType,
      int resultSetConcurrency,
      int resultSetHoldability)
      throws SQLException {
      throw new UnsupportedOperationException("Connection.prepareCall(String,int,int,int) unsupported");
   }
   
   
   public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
      throw new UnsupportedOperationException("Connection.prepareStatement(String,int) unsupported");
   }
   
   
   public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
      throw new UnsupportedOperationException("Connection.prepareStatement(String,int[]) unsupported");
   }
   
   
   public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
      throw new UnsupportedOperationException("Connection.prepareStatement(String,String[]) unsupported");
   }
   
   
   //---------------------------------------------------------------------
   // Properties
   //---------------------------------------------------------------------
   /**
    * Accessor method for the path property
    * @return current value for the path property
    */
   protected String getPath() {
      return path;
   }
   
   
   /**
    * Accessor method for the extension property
    * @return current value for the extension property
    */
   protected String getExtension() {
      if(extension.equalsIgnoreCase(WsvDriver.NO_EXTENSION))
         extension = "";
      else if(!extension.startsWith(".")){
         extension = "." + extension;
      }
      return extension;
   }
   
   
   /**
    * Accessor method for the separator property
    * @return current value for the separator property
    */
	protected String getSeperator() {
		return separator;
	}

   
   /**
    * Accessor method for the suppressHeaders property
    * @return current value for the suppressHeaders property
    */
   protected boolean isSuppressHeaders() {
      return suppressHeaders;
   }
   
   
   /**
    * Accessor method for the charset property
    * @return current value for the charset property
    */
   protected String getCharset() {
      return charset;
   }
   
      
   public String[] getColumnNames(){
   	return columnNames;
   }
   
   
	public String getColumnName(int i){
   	String result;
   	//
   	result = null;
   	if(columnNames != null && (-1<i && i<columnNames.length))
   		result = columnNames[i];
   	return result;
   }
   
   
   public Class[] getColumnTypes(){
   	return columnTypes;
   }
   
   
	public Class getColumnType(int i){
		Class result;
		//
		result = null;
		if(columnTypes != null && (-1<i && i<columnTypes.length))
			result = columnTypes[i];
		return result;
	}
	
	
	public Class getColumnTypeByName(String columnName){
		Class result;
		//
		result = null;
		if (columnTypes != null) {
			int i = -1;
			for (i = 0; i < columnNames.length; i++) {
				if (columnNames[i].equals(columnName))
					break;
			}
			result = columnTypes[i];
		}
		return result;
	}
	
	
	public String[] getColumnBounds() {
		return columnBounds;
	}
	
	
	public Integer[] getColumnBeginBoundaries() 
	throws SQLException{
		return getColumnBoundaries("begin");
	}
	
	
	public Integer[] getColumnEndBoundaries() 
	throws SQLException{				
		return getColumnBoundaries("end");
	}


	private Integer[] getColumnBoundaries(String type) 
	throws SQLException {
		Integer[] result;
		//
		result = null;
		if (columnBounds != null) {
			result = new Integer[columnBounds.length];
			for (int i = 0; i < columnBounds.length; i++) {
				if(type.equals("begin"))
					result[i] = getColumnBeginBound(i);
				if(type.equals("end"))
					result[i] = getColumnEndBound(i);
				// else result[i] == null
				if(result[i]==null || result[i].equals(""))
					throw new SQLException("Invalid fixed lenght specification: all ranges must be specified (" + i + "th is missing) "); 				
			}
		}
		return result;
	}
		
	
	Integer getColumnBeginBound(int i){
		return getBound(i, "begin");
	}
	
	
	Integer getColumnEndBound(int i){
		return getBound(i, "end");
	}
	

	private Integer getBound(int    i,
							 String type) {
		Integer result;
		result = null;
		if(columnBounds != null && (-1<i && i<columnBounds.length)){
			int sep;
			//
			sep 	 = columnBounds[i].indexOf("-");
			if(type.equals("begin"))
				result = Integer.valueOf(columnBounds[i].substring(0, sep));
			if(type.equals("end"))
				result = Integer.valueOf(columnBounds[i].substring(sep+1));			
			// else it'll be result == null
		}
		return result;
	}


	public Integer[] getColumnWidths() {
		return columnWidths;
	}
	
	
	public Integer getColumnWidth(int i){
		Integer result;
		//
		result = null;
		if(columnWidths != null && (-1<i && i<columnWidths.length)){
			if(columnTypes[i] == String.class)						
				result = columnWidths[i];
		}			
		return result;
	}

	
   /* (non-Javadoc)
    * @see java.sql.Connection#setSavepoint()
    */
   public Savepoint setSavepoint() throws SQLException {
      throw new UnsupportedOperationException();
   }
   
   
   /* (non-Javadoc)
    * @see java.sql.Connection#releaseSavepoint(java.sql.Savepoint)
    */
   public void releaseSavepoint(Savepoint savepoint) throws SQLException {
      throw new UnsupportedOperationException();
   }
   
   
   /* (non-Javadoc)
    * @see java.sql.Connection#rollback(java.sql.Savepoint)
    */
   public void rollback(Savepoint savepoint) throws SQLException {
      throw new UnsupportedOperationException();
   }
   
   
   /* (non-Javadoc)
    * @see java.sql.Connection#setSavepoint(java.lang.String)
    */
   public Savepoint setSavepoint(String name) throws SQLException {
      throw new UnsupportedOperationException();
   }
   
   
	/**
	 * @return int how many lines (at the beginning) have to be skipped
	 */
	public int getSkipLines() {
		return skipLines;
	}

	public String[] getDateFormats() {
		return dateFormats;
	}

   public boolean[] getTrimIfString() {      
      return trimIfVarchar;
   }

   private void setTrimIfString(boolean[] trimIfString) {
      this.trimIfVarchar = trimIfString;
   }


@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
	// TODO Auto-generated method stub
	return false;
}


@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public void abort(Executor executor) throws SQLException {
	// TODO Auto-generated method stub
	
}


@Override
public Array createArrayOf(String typeName, Object[] elements)
		throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public Blob createBlob() throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public Clob createClob() throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public NClob createNClob() throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public SQLXML createSQLXML() throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public Struct createStruct(String typeName, Object[] attributes)
		throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public Properties getClientInfo() throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public String getClientInfo(String name) throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public int getNetworkTimeout() throws SQLException {
	// TODO Auto-generated method stub
	return 0;
}


@Override
public String getSchema() throws SQLException {
	// TODO Auto-generated method stub
	return null;
}


@Override
public boolean isValid(int timeout) throws SQLException {
	// TODO Auto-generated method stub
	return false;
}


@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
	// TODO Auto-generated method stub
	
}


@Override
public void setClientInfo(String name, String value)
		throws SQLClientInfoException {
	// TODO Auto-generated method stub
	
}


@Override
public void setNetworkTimeout(Executor executor, int milliseconds)
		throws SQLException {
	// TODO Auto-generated method stub
	
}


@Override
public void setSchema(String schema) throws SQLException {
	// TODO Auto-generated method stub
	
}

}
